/*
 * Jamus
 * Copyright (C) 2010 Jamus Team
 *
 * Jamus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jamus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jamus.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __BACKEND_INTERFACE
#define __BACKEND_INTERFACE

#include <string>
#include <iostream>
#include <set>
#include <list>
#include <boost/property_tree/ptree.hpp>
#include <boost/signals2.hpp>

#include "../Track.h"

namespace backend {

/**
 * Interface for the back-end plugins.
 *
 * The interface methods must throw the specified exceptions on precondition
 * violations, but may additionally throw any other exception if the invoked
 * operations fail.
 */
class Plugin
{
public:
	virtual ~Plugin() {}

	void sendPlaybackStatusChanged() { this->sigPlaybackStatusChanged(); }
	void sendPlaylistChanged() { this->sigPlaylistChanged(); }
	void sendVolumeChanged() { this->sigVolumeChanged(); }

	/**
	 * Waits for changes and fires signals when appropriate. Signals may only
	 * be fired by the thread in this method.
	 */
	virtual void run() = 0;

	/**
	 * Tells the back-end to terminate. Calls to run() should return
	 * afterwards.
	 */
	virtual void cancel() = 0;

	/*
	 * Signals
	 * Slots will only be called by the thread that is calling run()
	 */

	typedef boost::signals2::signal<void ()> PlaylistChangedSignal;
	typedef PlaylistChangedSignal::slot_type PlaylistChangedSlot;

	/**
	 * slot will be called when the playlist changes.
	 */
	boost::signals2::connection connectPlaylistChanged(const PlaylistChangedSlot& slot)
	{
		return sigPlaylistChanged.connect(slot);
	}

	typedef boost::signals2::signal<void ()> PlaybackStatusChangedSignal;
	typedef PlaybackStatusChangedSignal::slot_type PlaybackStatusChangedSlot;

	/**
	 * slot will be called when the playback status or the current track
	 * changes, or when a seek occured.
	 */
	boost::signals2::connection connectPlaybackStatusChanged(const PlaybackStatusChangedSlot& slot)
	{
		return sigPlaybackStatusChanged.connect(slot);
	}

	typedef boost::signals2::signal<void ()> VolumeChangedSignal;
	typedef VolumeChangedSignal::slot_type VolumeChangedSlot;

	/**
	 * slot will be called when the volume changes
	 */
	boost::signals2::connection connectVolumeChanged(const VolumeChangedSlot& slot)
	{
		return sigVolumeChanged.connect(slot);
	}

	// ************************************************************************
	// The actual back-end interface starts here
	// None of these commands may directly fire signals. Only the thread in
	// run() may do so to avoid deadlocks if the front-end uses one mutex in
	// both the signal handler slots and the methods calling back-end methods.
	// ************************************************************************

	/*
	 * Playback
	 */
	enum PlaybackStatus {
		STOP,
		PLAY,
		PAUSE
	};

	virtual PlaybackStatus getPlaybackStatus() = 0;

	/**
	 * Resumes playback of the current track if stopped or paused. Has no
	 * effect if already playing. The current track does not change.
	 * If successful, emits PlaybackStatusChangedSignal.
	 * 
	 * @throw NoCurrentTrackException if there is no current track
	 */
	virtual void play() = 0;

	/**
	 * Starts playback of the specified track from the beginning.
	 * If successful, emits PlaybackStatusChangedSignal.
	 * Postcondition: If successful, current track is the specified track.
	 *
	 * @param track playlist index (beginning at 0) of the track to play.
	 */
	virtual void play(int track) = 0;

	/**
	 * Pauses playback. Use play() or playPause() to resume.
	 * If successful, emits PlaybackStatusChangedSignal.
	 *
	 * @throw NoCurrentTrackException if there is no current track
	 * @throw InvalidPlaybackStateException if there is a current track, but
	 * playback state is not playing
	 */
	virtual void pause() = 0;

	/**
	 * Stops playback. If already stopped, it has no effect. If there was a
	 * current track, it is still the current track.
	 * If successful, emits PlaybackStatusChangedSignal.
	 */
	virtual void stop() = 0;

	/**
	 * If playing, pause. Otherwise resume (or start) playback.
	 * If successful, emits PlaybackStatusChangedSignal.
	 *
	 * @throw NoCurrentTrackException if there is no current track
	 */
	virtual void playPause() = 0;

	/**
	 * Increments the current track and starts playback if not playing.
	 * If successful, emits PlaybackStatusChangedSignal.
	 *
	 * @throw NoCurrentTrackException if there is no current track
	 * @throw std::range_error if the current track is the last
	 */
	virtual void nextTrack() = 0;

	/**
	 * Decrements the current track and starts playback if not playing.
	 * If successful, emits PlaybackStatusChangedSignal.
	 *
	 * @throw NoCurrentTrackException if there is no current track
	 * @throw std::range_error if the current track is the first
	 */
	virtual void prevTrack() = 0;

	/**
	 * Returns the current track. "Current" does not necessarily mean that it
	 * is playing right now. It could also be stopped or paused.
	 *
	 * @return Playlist index of the current track (beginning at 0)
	 * @throw NoCurrentTrackException if there is no current track
	 */
	virtual int getCurrentTrack() = 0;

	/**
	 * @return Elapsed time of the current track
	 * @throw NoCurrentTrackException if there is no current track
	 */
	virtual boost::posix_time::time_duration getElapsed() = 0;

	/**
	 * If playing or paused, seeks to the specified position in the current
	 * track. If successful, emits PlaybackStatusChangedSignal.
	 *
	 * @throw NoCurrentTrackException if there is no current track
	 * @throw InvalidPlaybackStateException if not playing or paused
	 */
	virtual void seek(const boost::posix_time::time_duration& pos) = 0;

	/*
	 * Mixer
	 */
	//! @return Volume (0-100)
	virtual int getVolume() = 0;

	//! @throws std::range_error if volume is not in [0,100]
	virtual void setVolume(int vol) = 0;

	/*
	 * Music library
	 */
	typedef std::vector<Track> TrackList;

	//! @return const reference to the list of all tracks in the DB
	// XXX: Concurrency bugs? Better return copies / smart pointers?
	virtual const TrackList& getTrackList() = 0;

	/*
	 * Current playlist
	 */
	typedef std::vector<Track> Playlist;

	//! @return the current playlist
	// XXX: Concurrency bugs? Better return copies / smart pointers?
	virtual const Playlist& getPlaylist() = 0;

	/**
	 * Adds the specified track to the current playlist.
	 * @throw std::range_error if no such track exists in the track list
	 */
	virtual void addToPlaylist(const std::string& fileName) = 0;

	/**
	 * Removes the track with the specified playlist index from the playlist.
	 * @throw std::range_error if no such track exists
	 */
	virtual void removeFromPlaylist(int index) = 0;

	//! Clears the playlist
	virtual void clearPlaylist() = 0;

protected:
	PlaylistChangedSignal sigPlaylistChanged;
	PlaybackStatusChangedSignal sigPlaybackStatusChanged;
	VolumeChangedSignal sigVolumeChanged;
};

typedef Plugin* create_p(const boost::property_tree::ptree& config);
typedef void destroy_p(Plugin* t);

} // namespace backend

#endif
